Skip to content

8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays#2569

Open
chhagedorn wants to merge 11 commits into
openjdk:lworldfrom
chhagedorn:JDK-8382226
Open

8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays#2569
chhagedorn wants to merge 11 commits into
openjdk:lworldfrom
chhagedorn:JDK-8382226

Conversation

@chhagedorn

@chhagedorn chhagedorn commented Jun 19, 2026

Copy link
Copy Markdown
Member

The provided test cases fail when inlining the Array.copyOf/copyOfRange() intrinsics where the source array is flat and from an abstract value class.

The current code checks whether the source array or the destination array klass contain oops by assuming that a flat value class array is always concrete and thus an InlineKlass (i.e. can call inline_klass()). However, we could also have abstract value class arrays that are known to be flat (see test cases). This leads to a cast assertion failure because abstract value classes are represented by an InstanceKlass and not an InlineKlass.

To fix this, I added a simple bailout when detecting an abstract flat value class array. This is a conservative correctness fix and should be revisited again post-Valhalla-integration. We have JDK-8251971 in place for that which should also tackle other issues around the arraycopy intrinsics and also address performance problems.

Thanks,
Christian



Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (1 review required, with at least 1 Committer)

Issue

  • JDK-8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/2569/head:pull/2569
$ git checkout pull/2569

Update a local copy of the PR:
$ git checkout pull/2569
$ git pull https://git.openjdk.org/valhalla.git pull/2569/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 2569

View PR using the GUI difftool:
$ git pr show -t 2569

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/2569.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper

bridgekeeper Bot commented Jun 19, 2026

Copy link
Copy Markdown

👋 Welcome back chagedorn! A progress list of the required criteria for merging this PR into lworld will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk

openjdk Bot commented Jun 19, 2026

Copy link
Copy Markdown

@chhagedorn This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays

Reviewed-by: qamai

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 7 new commits pushed to the lworld branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the lworld branch, type /integrate in a new comment.

@openjdk openjdk Bot added the rfr Pull request is ready for review label Jun 19, 2026
@mlbridge

mlbridge Bot commented Jun 19, 2026

Copy link
Copy Markdown

Webrevs

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr();
const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr();
bool exclude_flat = UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) &&
const bool is_src_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && orig_t->is_flat();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for !orig_t->is_not_flat() instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea to cover all the cases where it could be flat. Updated this and the check for tklass as well accordingly.

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
(orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) &&
// Can dest array be flat and contain oops?
tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
can_dest_be_value_class_array && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops())));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also check for tklass->is_not_flat() instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I also took another look at the code again and refactored it a bit to make exclude_flat easier to understand. Let me know, what you think.

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr();
const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat();
const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if dest is not an aryklassptr? I think in that case this should also be true, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See general comment sent above.

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
// Can dest array be flat and contain oops?
tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr();
const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is a little misleading, I think you want to catch the case orig_t == nullptr below, but if the static type is j.l.O, the runtime type can still be an abstract flat array.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. Though, I'm wondering if orig_t could even be a non-array pointer at all. The Arrays.copyOf() takes a U[], so after type erasure, we should know it's an Object[] and thus have an array
pointer. It looks like the original code was just defensive by checking orig_t as well. I removed it and testing passed.

@chhagedorn chhagedorn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking some more time to respond here. Thanks for your additional questions @merykitty. I've had another closer look at the code and wrote some more tests and discovered more issues:

  1. When passing in primitive type mirrors like int.class or for void.class to Arrays.copyOf(), there is no klass field and we return null:
    if (!t->is_klass()) {
    // a primitive Class (e.g., int.class) has null for a klass field
    return TypePtr::NULL_PTR;
    }

    As a result, we crash here because klass_node returned from load_klass_from_mirror() is top:
    const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr();
  • Solution: Bail out if stopped() after load_klass_from_mirror().
  1. When passing in an instance class mirror like A.class we should throw because it's not an array class. However, with the checks in the current code, we only emit a type array guard which does not bail out when having an instance klass:
    bool exclude_flat = UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) &&
    // Can src array be flat and contain oops?
    (orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) &&
    // Can dest array be flat and contain oops?
    tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
    Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout);

We then try to load the refined array klass from an instance klass pointer in load_default_refined_array_klass() where we read from ObjArrayKlass::next_refined_array_klass_offset() which is garbage.

  • Solution: Emit generate_non_refArray_guard() when the klass pointer is not an array klass.
  1. We need to check UseArrayFlattening and the GC barrier availability after the inserted check for for 2., otherwise, we hit the same problem again when disabling UseArrayFlattening or not requiring GC barriers at all.

I pushed an update with the following changes:

  • Refactored bailout logic again to a separate method where I check all the cases systematically:
    • Check dest klass to be an array klass.
    • Check all cases for maybe flat and bail out which also covers j.l.Object.
    • Check write barriers.
  • Added more comments.
  • Added a lot more tests by using the Template Framework.

Testing this through t1-4 + stress looked good. Since we now bail out in more cases, I might also run some performance testing.

Let me know, what you think.

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
// Can dest array be flat and contain oops?
tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr();
const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. Though, I'm wondering if orig_t could even be a non-array pointer at all. The Arrays.copyOf() takes a U[], so after type erasure, we should know it's an Object[] and thus have an array
pointer. It looks like the original code was just defensive by checking orig_t as well. I removed it and testing passed.

Comment thread src/hotspot/share/opto/library_call.cpp Outdated
tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops());
const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr();
const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat();
const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See general comment sent above.

@merykitty merykitty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking further look, it looks much better now.

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Jul 1, 2026
@chhagedorn

Copy link
Copy Markdown
Member Author

Thanks @merykitty for your careful review!

// Arrays.copyOf() uses a generic Class parameter which is erased to the raw type Class. This also allows
// passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers.
// In these cases, klass_node will be top. Emit a trap to throw in the interpreter in this case.
bail_out_from_array_copyOf(bailout);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the j.l.Class is not a constant, but the runtime value is int.class, it will return null, right? Which explains the null check below. In that case, is it an inconsistency? Can it be an issue if a j.l.Class is narrowed to the constant int.class during IGVN, then the node will become top and the whole graph is incorrectly killed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Pull request is ready to be integrated rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

2 participants